/**************************************************************************************
*
*	Copyright (C) 2009 - 2012 Brent Andrew O. Chua - See LICENSE.txt for more info.
*		
*	Filename	-	Texture2D.cpp
*
*	Comments	-	See Texture2D.h
*
**************************************************************************************/
#include "../Include/Texture2D.h"
#include "../Include/Engine.h"
#include "../Include/FileSystem.h"
#include "../Include/ResourceCache.h"
#include "../Include/GraphicsDevice.h"
#include "../Include/AsyncLoadResource.h"
#include "../Include/GlobalDirectory.h"

// Third party includes
#include "../External/DirectXTools/DirectXTK/Inc/WICTextureLoader.h"
//#include "../External/DirectXTools/DirectXTK/Inc/DDSTextureLoader.h" // For loading DDS files

#ifdef PSX_DEBUG
#pragma comment( lib, "DirectXTKd.lib" )
#else
#pragma comment( lib, "DirectXTK.lib" )
#endif

namespace Pulse
{
	Texture2DDesc::Texture2DDesc( void )
	{
		m_pDSVConfig = PSX_NULL;
		m_pSRVConfig = PSX_NULL;

		Clear();
	}

	Texture2DDesc::~Texture2DDesc( void )
	{
		SetDepthStencilView( PSX_NULL );
		SetShaderResourceView( PSX_NULL );
	}

	void Texture2DDesc::Set( ETextureUsage::Type _usage, EGraphicsFormat::Type _format, UINT _width, UINT _height, UINT _arraySize )
	{
		Clear();

		format = _format;
		usage = _usage;
		width = _width;
		height = _height;
		arraySize = _arraySize;
	}

	void Texture2DDesc::SetToLoad( const CHAR *pFilename )
	{
		Clear();
		filename = pFilename;
	}

	void Texture2DDesc::SetAsCubeMap( const SIZE_T size, const BOOL bDynamic )
	{
		Clear();

		format;
		usage;
		width;
		height;
		arraySize = 6; // Cube has 6 sides
		bGenerateMips;
		//bTe
	}

	void Texture2DDesc::SetAsDepthBuffer( UINT _width, UINT _height, EGraphicsFormat::Type _format, BOOL bBindAsResource )
	{
		Clear();

		width = _width;
		height = _height;
		format = _format;
		usage = bBindAsResource ? ETextureUsage::DEPTHBUFFER_BINDABLE : ETextureUsage::DEPTHBUFFER;
	}

	void Texture2DDesc::SetAsRenderTarget( UINT _width, UINT _height )
	{
		Clear();

		width = _width;
		height = _height;
		format = EGraphicsFormat::R8G8B8A8_SNORM;
		usage = ETextureUsage::RENDERTARGET;
	}

	void Texture2DDesc::SetAsSwapChainBuffer( void *pSwapChainBuffer )
	{
		Clear();
		Engine *pEngine = Engine::GetInstance();
		const DisplayMode *pMode = pEngine->GetDisplaySettings();

		usage = ETextureUsage::SWAPCHAINBUFFER;
		pData = (void*)pSwapChainBuffer;
	}

	void Texture2DDesc::Clear( void )
	{
		filename.Clear();
		format = EGraphicsFormat::UNKNOWN;
		usage = ETextureUsage::UNKNOWN;
		width = 0;
		height = 0;
		arraySize = 1;
		bGenerateMips = FALSE;
		bTextureCube = FALSE;
		pData = PSX_NULL;

		SetDepthStencilView( PSX_NULL );
		SetShaderResourceView( PSX_NULL );
	}

	void Texture2DDesc::SetDepthStencilView( const DepthStencilViewDesc *pView )
	{
		PSX_SafeDelete( m_pDSVConfig );

		if ( pView )
		{
			m_pDSVConfig = new DepthStencilViewDesc;
			*m_pDSVConfig = *pView;
		}
	}

	void Texture2DDesc::SetShaderResourceView( const ShaderResourceViewDesc *pView )
	{
		PSX_SafeDelete( m_pSRVConfig );

		if ( pView )
		{
			m_pSRVConfig = new ShaderResourceViewDesc;
			*m_pSRVConfig  = *pView;
		}
	}

	Texture2DDesc & Texture2DDesc::operator = ( const Texture2DDesc &rhs )
	{
		filename = rhs.filename;
		format = rhs.format;
		usage = rhs.usage;
		width = rhs.width;
		height = rhs.height;
		arraySize = rhs.arraySize;
		bGenerateMips = rhs.bGenerateMips;
		bTextureCube = rhs.bTextureCube;
		pData = rhs.pData;

		// Remember we dynamically allocate the view descs. Don't shallow copy.
		SetDepthStencilView( rhs.m_pDSVConfig );
		SetShaderResourceView( rhs.m_pSRVConfig );

		return *this;
	}

	Texture2D::Texture2D( void )
	{
	}

	Texture2D::~Texture2D( void )
	{
	}

	BOOL Texture2D::CreateResource( BaseResDesc *pDesc )	
	{
		if ( pDesc->GetType() != GetType() )
			return FALSE;

		m_desc = *((Texture2DDesc*)pDesc);

		if ( PerformInitialize( m_desc.IsAsyncLoad() ) == EErrorCode::OKAY )
			SetFlagCreated();

		return IsCreated();	
	}
	
	BOOL Texture2D::DestroyResource( void )				
	{
		UnloadResource();
		SetFlagDestroyed();	
		return TRUE;	
	}
	
	BOOL Texture2D::LoadResource( void )				
	{ 
		if ( Initialize() == EErrorCode::OKAY )
		{
			SetFlagLoaded();	
			return TRUE;	
		}

		return FALSE;
	}

	BOOL Texture2D::UnloadResource( void )				
	{
		Cleanup();
		SetFlagUnloaded();
		return FALSE;	
	}

	EErrorCode::Type Texture2D::Initialize( void )
	{
		ID3D11Device *pDevice = (ID3D11Device*)GraphicsDevice::GetInstance()->GetNakedDevice();

		if ( m_desc.filename.IsEmpty() == FALSE ) // Load texture
		{
			// Format string
			String fullPath = PSX_PATH_TEXTURES + m_desc.filename;

			// Load texture in memory
			FileHandlePtr hFile = FileSystem::GetInstance()->OpenFile( fullPath.GetBuffer() );

			if ( !hFile->IsValid() )
			{
				PSX_PushError( "Failed to load texture file: %s", m_desc.filename.GetBuffer() );
				return EErrorCode::FILE_NOT_FOUND;
			}

			ResHandlePtr hResource = Engine::GetInstance()->GetResourceCache()->GetHandle( hFile );

			m_pTexture = PSX_NULL;

			//HRESULT hr = D3DX11CreateTextureFromMemory( pDevice, hResource->GetBuffer(), hResource->GetSize(), PSX_NULL,
			//	PSX_NULL, (ID3D11Resource**)&m_pTexture, PSX_NULL );

			HRESULT hr = DirectX::CreateWICTextureFromMemory( pDevice, (ID3D11DeviceContext*)GraphicsDevice::GetInstance()->GetNakedDeviceContext(),
				hResource->GetBuffer(), hResource->GetSize(), (ID3D11Resource**)&m_pTexture, PSX_NULL );

			if ( FAILED( hr ) )
			{
				PSX_PushError( "Failed to load texture file: %s", m_desc.filename.GetBuffer() );
				return EErrorCode::GRAPHICS;
			}

			D3D11_TEXTURE2D_DESC desc;
			m_pTexture->GetDesc( &desc );

			m_desc.format = DX11Mapping::ToBufferFormat( desc.Format );
			m_desc.usage = ETextureUsage::STATIC;
			m_desc.width = desc.Width;
			m_desc.height = desc.Height;

		}
		else	// Create texture
		{
			D3D11_TEXTURE2D_DESC desc;

			if ( m_desc.usage == ETextureUsage::SWAPCHAINBUFFER )
			{
				m_pTexture = (ID3D11Texture2D*)m_desc.pData;
				m_pTexture->AddRef();

				D3D11_TEXTURE2D_DESC texDesc;
				m_pTexture->GetDesc( &texDesc );

				m_desc.format = DX11Mapping::ToBufferFormat( texDesc.Format );
				//m_desc.usage = ETextureUsage::SWAPCHAINBUFFER;
				m_desc.width = texDesc.Width;
				m_desc.height = texDesc.Height;
				m_desc.arraySize = texDesc.ArraySize;
				m_desc.bGenerateMips = texDesc.MipLevels == 1 ? FALSE : TRUE;
				m_desc.pData = PSX_NULL;

			}
			else
			{
				desc.Width = (UINT)m_desc.width;
				desc.Height = (UINT)m_desc.height;
				desc.MipLevels = GetDescMipLevels();
				desc.ArraySize = (UINT)m_desc.arraySize;
				desc.Format = DX11Mapping::ToDXBufferFormat( m_desc.format );
				desc.SampleDesc.Count = 1;
				desc.SampleDesc.Quality = 0;
				desc.Usage = GetDescUsage();
				desc.BindFlags = GetDescBindFlags();
				desc.CPUAccessFlags = GetDescCPUUsageFlags();
				desc.MiscFlags = GetDescMiscFlags();

				HRESULT hr;

				if ( m_desc.pData )
				{
					D3D11_SUBRESOURCE_DATA data;
					data.pSysMem = m_desc.pData;

					hr = pDevice->CreateTexture2D( &desc, &data, &m_pTexture );
				}
				else
				{
					hr = pDevice->CreateTexture2D( &desc, PSX_NULL, &m_pTexture );
				}

				if ( FAILED( hr ) )
				{
					PSX_PushError( "Failed to create texture." );
					return EErrorCode::GRAPHICS;
				}
			}
		}

		D3D11_TEXTURE2D_DESC desc;
		m_pTexture->GetDesc( &desc );
		//CreateResourceViews( (ID3D11Resource*)m_pTexture, desc.BindFlags );
		CreateResourceViews( (ID3D11Resource*)m_pTexture, desc.BindFlags, 
			m_desc.m_pDSVConfig, m_desc.m_pSRVConfig, PSX_NULL );

		return EErrorCode::OKAY;
	}

	void Texture2D::Cleanup( void )
	{
		ReleaseResourceViews();
		PSX_SafeRelease( m_pTexture );
	}

	UINT Texture2D::GetDescMipLevels( void )
	{
		if ( m_desc.usage == ETextureUsage::RENDERTARGET )
			return 1;
		else if ( m_desc.usage == ETextureUsage::DYNAMIC )
			return 1;
		else if ( m_desc.usage == ETextureUsage::STAGING )
			return 1;
		else if ( m_desc.usage == ETextureUsage::NORMAL )
			return 0;
		else if ( m_desc.usage == ETextureUsage::STATIC )
			return 0;
		else if ( m_desc.usage == ETextureUsage::DEPTHBUFFER ||
			m_desc.usage == ETextureUsage::DEPTHBUFFER_BINDABLE )
			return 0;
		else
			return 0;

	}

	D3D11_USAGE Texture2D::GetDescUsage( void )
	{
		switch ( m_desc.usage )
		{
		case ETextureUsage::DYNAMIC:
			return D3D11_USAGE_DYNAMIC;
		case ETextureUsage::STAGING:
			return D3D11_USAGE_STAGING;
		case ETextureUsage::STATIC:
			return D3D11_USAGE_IMMUTABLE;
		case ETextureUsage::DEPTHBUFFER:
		case ETextureUsage::DEPTHBUFFER_BINDABLE:
		case ETextureUsage::RENDERTARGET:
		case ETextureUsage::NORMAL:
		default:
			return D3D11_USAGE_DEFAULT;
		}
	}

	UINT Texture2D::GetDescBindFlags( void )
	{
		UINT flags = 0;
		
		if ( m_desc.usage == ETextureUsage::NORMAL || 
			m_desc.usage == ETextureUsage::RENDERTARGET )
			flags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
		else if ( m_desc.usage == ETextureUsage::DYNAMIC )
			flags = D3D11_BIND_SHADER_RESOURCE;
		else if ( m_desc.usage == ETextureUsage::STAGING )
			flags = D3D11_BIND_SHADER_RESOURCE; // NOTE: Not sure what for staging
		else if ( m_desc.usage == ETextureUsage::STATIC )
			flags = D3D11_BIND_SHADER_RESOURCE;
		else if  ( m_desc.usage == ETextureUsage::DEPTHBUFFER )
			flags =  D3D11_BIND_DEPTH_STENCIL;
		else if ( m_desc.usage == ETextureUsage::DEPTHBUFFER_BINDABLE )
			flags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE;
		return flags;
	}

	UINT Texture2D::GetDescCPUUsageFlags( void )
	{
		switch ( m_desc.usage )
		{
		case ETextureUsage::STAGING:
			return D3D11_CPU_ACCESS_READ;
		case ETextureUsage::DYNAMIC:
			return D3D11_CPU_ACCESS_WRITE;
		case ETextureUsage::STATIC:
		case ETextureUsage::DEPTHBUFFER:
		case ETextureUsage::RENDERTARGET:
		case ETextureUsage::NORMAL:
		default:
			return 0;
		}
	}

	UINT Texture2D::GetDescMiscFlags( void )
	{
		UINT flags = 0;

		if ( m_desc.bGenerateMips )
			flags |= D3D11_RESOURCE_MISC_GENERATE_MIPS;

		if ( m_desc.bTextureCube )
			flags |= D3D11_RESOURCE_MISC_TEXTURECUBE;

		return flags;
	}

}